#include "GUIBase.h"
#include <QtGui/qpainter.h>
#include <QtGui/qbitmap.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <qdebug.h>
#include <math.h>
#include <string>

#define PI 3.1415926

AnimationView* GUIBase::factoryScene;

GUIBase::GUIBase(QGraphicsItem * parent) : QGraphicsObject(parent)
{
	//constructor sets default vaues 
	this->setZValue(ZVALUE_GROUND);
	originAtCenter = false;
	speed= 1;
	acceleration = 0;
	speedLimit = 1;
	rotationSpeed=1;
	clockwiseRotation = true;
	imgScale = 1.0;
	scaleSpeed = 0.01;
	moving=false;
	rotating=false;
	scaling=false;
	movingInstantly=false;
	rotatingInstantly=false;
	currentAnim=0;
	currentFrame=0;
	broken = false;
	blackAndWhite = false;
	factoryScene->addItem(this);
}

GUIBase::~GUIBase()
{
	//empty deconstructor 
}

void GUIBase::setAgent(Agent* agent)
{ 
	this->setGUIBaseAgent(agent);
}

void GUIBase::setGUIBaseAgent(Agent* agent)
{  //matches GUI with correct agent 
	this->agent = agent;
}

Agent* GUIBase::getGUIBaseAgent()
{ //returns agent that correlates with gui item 
	return agent;
}


void convertToGrayscale(QPixmap& pixmap)
{	//before user clicks start, the entire factory is in grayscale 
	QImage image = pixmap.toImage();
	QRgb col;
	int gray;
	int width = pixmap.width();
	int height = pixmap.height();
	for (int i = 0; i < width; ++i)
	{
		for (int j = 0; j < height; ++j)
		{
			col = image.pixel(i, j);
			if (qAlpha(col) > 15)
			{
				gray = qGray(col);
				image.setPixel(i, j, qRgb(gray, gray, gray));
			}
		}
	}
	pixmap = pixmap.fromImage(image);
}

void GUIBase::loadImage(const char *imgPath)
{ //used in inherited classes to load image 
	QList<Frame> newImg;
	Frame newFrame(this);
	QPixmap newPixMap = QPixmap(imgPath);
	newFrame.img[0] = newPixMap;
	newFrame.img[1] = newPixMap; convertToGrayscale(newFrame.img[1]);
	newFrame.rect = newPixMap.rect();
	newImg << newFrame;
	animations << newImg;
}

void GUIBase::loadMultImages(std::string animPath)
{ //used to load multiple images 
	animPath.append("*.png");
	QFileInfo info(animPath.c_str());
	QStringList entries = QDir(info.path(), info.fileName()).entryList();
	QList<Frame> newAnim;
	for(int i = 0; i < entries.size(); i++)
	{
		Frame newFrame(this);
		QPixmap temp = QPixmap(info.path()+"/"+entries[i]);
		newFrame.img[0] = temp;
		newFrame.img[1] = temp; convertToGrayscale(newFrame.img[1]);
		newFrame.rect = temp.rect();
		newAnim << newFrame;
	}
	animations << newAnim;
}


void GUIBase::setOriginCenter()
{ //instead of top left corner which is default; this function makes rotating easier 
	this->originAtCenter = true;
}

void GUIBase::setOriginTopLeft()
{ //revert back to default 
	this->originAtCenter = false;
}


void GUIBase::setMovementSpeed(qreal sp)
{
	speed = sp; //sets speed for movement 
}

void GUIBase::setRotationSpeed(qreal rotSpeed)
{
	rotationSpeed = rotSpeed; //sets speed for rotation, used when object rotates over time
}


void GUIBase::setScaleSpeed(qreal newScaleSpeed)
{
	scaleSpeed = newScaleSpeed; //sets scaling speed (when objects grow/shrink)
}

void GUIBase::moveTo(qreal destX, qreal destY)
{ //called to move object to destination location 
	destination = QPointF(destX, destY);
	acceleration = 0;
	qreal dist = distance(pos(), destination);
	direction = QPointF((destX - pos().x()) / dist,(destY - pos().y()) / dist);
	moving=true;
	emit signalBeginMoving(destX, destY);
}

 /*accelTo() The start speed will be the one set with setMovementSpeed().
  speedLimit is either an upper or lower bound on the speed depending on the sign of accel.
 If the part is decelerating (and its lower bound is < 0) and never makes it to it's destination it will accelerate forever in the opposite dir.
 If the speed limit is 0 and it doesn't make it to the destination, the doneMoving callback will be fired and it will be marked as not moving. */

void GUIBase::accelTo(qreal destX, qreal destY, qreal accel, qreal speedLimit)
{
	destination = QPointF(destX, destY);
	acceleration = accel;
	this->speedLimit = speedLimit;
	qreal dist = distance(pos(), destination);
	direction = QPointF((destX - pos().x()) / dist,(destY - pos().y()) / dist);
	moving=true;
	emit signalBeginMoving(destX, destY);
}

void GUIBase::advance(int phase)
{ //used/overwritten in inherited classes
	if (phase == 1)
	{
		move();
		rotateStep();
		scaleStep();

		if(movingInstantly)
			this->setPos(destination);
		if(rotatingInstantly){
			this->setRotation(targetRotation);
		}
		movingInstantly=false;
		rotatingInstantly=false;
	}
}

void GUIBase::move()
{
	if(moving)
	{
		qreal dx = speed*direction.x();
		qreal dy = speed*direction.y();
		qreal newX = x();
		qreal newY = y();
		if((direction.x() > 0 && newX<destination.x()) || (direction.x() < 0 && newX > destination.x()) || ((direction.y() > 0 && newY < destination.y() ) || (direction.y() < 0 && newY > destination.y())))	//We are not at our y position
		{

			this->setPos(newX + dx, newY + dy);
		}
		else
		{
			dx = destination.x() - x();
			dy = destination.y() - y();
			this->setPos(destination.x(), destination.y());
			moving=false;
			emit signalDoneMoving(destination.x(), destination.y());
			this->doneMoving();
			this->doneMoving(destination.x(), destination.y());
		}

		if (acceleration != 0)
		{
			speed += acceleration;
			if ((acceleration > 0 && speed > speedLimit) || (acceleration < 0 && speed < speedLimit))
			{
				speed = speedLimit;
				acceleration = 0;
			}
		}
		if (acceleration == 0 && speed == 0)
		{
			moving = false;
			emit signalDoneMoving(destination.x(), destination.y());
			this->doneMoving();
			this->doneMoving(destination.x(), destination.y());
		}
	}
}

void GUIBase::moveInstantlyTo(qreal newX, qreal newY)
{ //transportation 
	movingInstantly=true;
	destination.setX(newX);
	destination.setY(newY);
}

void GUIBase::rotateInstantlyTo(qreal degrees)
{  //no time delay 
	rotatingInstantly=true;
	targetRotation = degrees;
}


void GUIBase::rotateTowards(qreal targetX, qreal targetY)
{ //rotates toward specific destination location 
	direction = QPointF((targetX - this->x()),(targetY - this->y()) );
	qreal angle = qAtan2(direction.x(),-direction.y());
	angle = angle*(180/PI);

	this->setRotation(angle);
}

void GUIBase::rotateTowardsOverTime(qreal targetX, qreal targetY, qreal startingX, qreal startingY)
{ //rotates toward a location over time rather than instantly 
	direction = QPointF((targetX - startingX), (targetY - startingY ));
	qreal angle = qAtan2(direction.x(),-direction.y());
	angle = angle*(180/PI);

	if(angle<0)
		angle=360+angle;

	qreal angDistCW = angle - rotation(); if (angDistCW > 360) angDistCW -= 360; if (angDistCW < 0) angDistCW += 360;
	qreal angDistCCW = (rotation() + 360) - angle; if (angDistCCW > 360) angDistCCW -= 360; if (angDistCCW < 0) angDistCCW += 360;

	rotateOverTime(angle, angDistCW <= angDistCCW);
}

void GUIBase::rotateOverTime(qreal degrees, bool clockwise)
{ //called from above, determines which direction 
	targetRotation = degrees;
	rotating = true;
	clockwiseRotation = clockwise;
}

void GUIBase::rotateStep()
{ //incremental rotate, called in advance function if rotating over time 
	if(rotating)
	{
		if (clockwiseRotation)
		{
			if ((rotation() <= targetRotation && targetRotation <= rotation() + rotationSpeed)
				|| (targetRotation <= rotation() && rotation() <= targetRotation + 360 && targetRotation + 360 <= rotation() + rotationSpeed))
			{
				this->setRotation(targetRotation);
				rotating=false;
				this->doneRotating();
				this->doneRotating(rotation());
			}
			else
			{
				this->setRotation(fmod(rotation() + rotationSpeed, 360));
			}
		}
		else
		{
			if(rotation() >= targetRotation && targetRotation >= rotation() - rotationSpeed)
			{
				this->setRotation(targetRotation);
				rotating=false;
				this->doneRotating();
				this->doneRotating(rotation());
			}
			else
			{
				qreal newAngle = rotation() - rotationSpeed;
				if (newAngle < 0) newAngle += 360;
				this->setRotation(newAngle);
			}
		}
	}
}


void GUIBase::scaleOverTime(qreal scale_factor)
{ //change size over time 
	scaling = true;
	targetScale = scale_factor;
}

void GUIBase::setTransformToCenter()
{
	this->setTransformOriginPoint(this->boundingRect().width()/2,this->boundingRect().height()/2);
}

void GUIBase::setTransformToTopLeft()
{
	this->setTransformOriginPoint(this->boundingRect().x(),this->boundingRect().y());
}

void GUIBase::scaleStep()
{ //incremental scaling over time, called by advance 
	if (scaling)
	{
		qreal newScale = imgScale;
		if (qAbs(imgScale - targetScale) < scaleSpeed)
		{
			newScale = targetScale;
			scaling = false;
			this->doneScaling();
			this->doneScaling(targetScale);
		}
		else
		{
			if (imgScale < this->targetScale)
			{
				newScale = imgScale + this->scaleSpeed;
			}
			else if (imgScale > this->targetScale)
			{
				newScale = imgScale - this->scaleSpeed;
			}
		}
		imgScale = newScale;
	}
}

void GUIBase::DoBreak()
{ //generic break function, called by button break 
	broken = true;
	blackAndWhite = true;
}

void GUIBase::DoFix()
{ //generic break function, called by button fix 
	broken = false;
	blackAndWhite = false;
}

QRectF GUIBase::boundingRect() const
{
	if(animations.size()>currentAnim&&currentAnim>=0)
		if(animations[currentAnim].size()>currentFrame&&currentFrame>=0)
			if(!animations[currentAnim][currentFrame].getImg().isNull())
				return animations[currentAnim][currentFrame].rect;
	
	return QRectF(this->pos().x(), this->pos().y(), 10, 10);
	//returns rectangle of pixmap, basically the size of the image 
}


void GUIBase::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{ //displays image on screen 
	painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
	if(animations.size()>currentAnim&&currentAnim>=0)
	{
		if(animations[currentAnim].size()>currentFrame&&currentFrame>=0)
		{
			if(!animations[currentAnim][currentFrame].getImg().isNull())
			{
				QSize size(animations[currentAnim][currentFrame].getImg().width() * imgScale, animations[currentAnim][currentFrame].getImg().height() * imgScale);
				QPixmap scaledPixmap(animations[currentAnim][currentFrame].getImg().scaled(size));

				if (!originAtCenter)
					painter->drawPixmap(0, 0, scaledPixmap);
				else
					painter->drawPixmap(-scaledPixmap.width()/2, -scaledPixmap.height()/2, scaledPixmap);
			}
		}
	}
}

//used for testing 
void GUIBase::debugPrint(char * msg)
{
	qDebug(msg);
}

qreal GUIBase::distance(QPointF point1, QPointF point2)
{
	//returns distance between two points 
	return qSqrt((qPow(point1.x() - point2.x(), 2) + qPow(point1.y() - point2.y(), 2)));
}